package com.waming.spring.sensitive.plugin.mybatis.interceptor;

import com.waming.spring.sensitive.plugin.annotation.EncryptEnabled;
import com.waming.spring.sensitive.plugin.encrypt.Encrypt;
import com.waming.spring.sensitive.plugin.handler.AnnotationHandler;
import com.waming.spring.sensitive.plugin.mybatis.enums.PolicyType;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.*;

/**
 * 拦截写请求的插件。插件生效仅支持预编译的sql
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
})
public class EncryptWriteInterceptor implements Interceptor {
    private static Logger log= LogManager.getLogger(EncryptWriteInterceptor.class);
    private static final String MAPPEDSTATEMENT="delegate.mappedStatement";
    private static final String BOUND_SQL="delegate.boundSql";
    private Encrypt encrypt;
    private final AnnotationHandler annotationCacheHandler;
    public EncryptWriteInterceptor(AnnotationHandler annotationCacheHandler, Encrypt encrypt) {
        Objects.requireNonNull(encrypt,"encrypt should not be null!");
        this.annotationCacheHandler = annotationCacheHandler;
        this.encrypt = encrypt;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = annotationCacheHandler.realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        MappedStatement mappedStatement = (MappedStatement)metaObject.getValue(MAPPEDSTATEMENT);
        SqlCommandType commandType = mappedStatement.getSqlCommandType();
        BoundSql boundSql = (BoundSql)metaObject.getValue(BOUND_SQL);
        Object params = boundSql.getParameterObject();
        try{
            if(params instanceof Map){
                multipleParamter((Map<?,?>)params,mappedStatement.getConfiguration(),boundSql,commandType);
            }else {
                EncryptEnabled encryptEnabled = params != null ? params.getClass().getAnnotation(EncryptEnabled.class) : null;
                if (encryptEnabled != null && encryptEnabled.value()) {
                    handleParameters(mappedStatement.getConfiguration(),boundSql, params, commandType);
                }
            }
        }catch (Exception e){
            log.error("SensitiveAndEncryptWriteInterceptor intercept fail",e);
        }
        return invocation.proceed();
    }

    /**
     * 多对象参数处理
     * @param paramMap
     * @param configuration
     * @param boundSql
     * @param commandType
     * @throws Exception
     */
    protected void multipleParamter(Map<?,?> paramMap,Configuration configuration,BoundSql boundSql,SqlCommandType commandType) throws Exception {
        Set<?> keySet = paramMap.keySet();
        Set<Object> repetition = new HashSet<>();
        // 处理过的对象记录
        for (Object key : keySet) {
            Object oj = paramMap.get(key);
            if (oj == null) {
                continue;
            }
            if (oj instanceof Collection) {
                if(!repetition.contains(oj)){
                    repetition.add(oj);
                }else{
                    continue;
                }
                Collection<?> collection=(Collection<?>) oj;
                Object item=collection.iterator().next();
                EncryptEnabled sensitiveEnabled = item != null ? item.getClass().getAnnotation(EncryptEnabled.class) : null;
                if(sensitiveEnabled != null && sensitiveEnabled.value()){
                    for (Object ob : collection) {
                        handleListParameters(configuration,ob);
                    }
                }
            } else {
                EncryptEnabled encryptEnabled = oj.getClass().getAnnotation(EncryptEnabled.class);
                if (encryptEnabled != null && encryptEnabled.value()) {
                    handleListParameters(configuration,oj);
                }
            }
        }
    }

    private void handleListParameters(Configuration configuration,Object param) throws Exception {
        MetaObject metaObject = configuration.newMetaObject(param);
        annotationCacheHandler.parseClass(param.getClass());
        //加解密属性
        Set<Field> fields =annotationCacheHandler.getCahceFields(param.getClass(), PolicyType.ENCRYPT);
        if(fields==null || fields.isEmpty()){
            return;
        }
        for (Field field : fields) {
            Object value = metaObject.getValue(field.getName());
            Object newValue = value;
            if(value instanceof CharSequence){
                newValue = handleEncryptField(newValue);
            }
            if(value!=null && newValue!=null && !value.equals(newValue)) {
                metaObject.setValue(field.getName(),newValue);
            }
        }
    }


    private void handleParameters(Configuration configuration,BoundSql boundSql,Object param,SqlCommandType commandType) throws Exception {
        Map<String, Object> newValues = new HashMap<>(16);
        MetaObject metaObject = configuration.newMetaObject(param);
        annotationCacheHandler.parseClass(param.getClass());
        Set<Field> fields =annotationCacheHandler.getCahceFields(param.getClass(), PolicyType.ENCRYPT);
        if(fields==null || fields.isEmpty()){
            return;
        }
        //加密属性
        for (Field field : fields) {
            Object value = metaObject.getValue(field.getName());
            Object newValue = value;
            if(value instanceof CharSequence){
                newValue = handleEncryptField(newValue);
            }
            if(value!=null && newValue!=null && !value.equals(newValue)) {
                newValues.put(field.getName(), newValue);
            }
        }
        for (Map.Entry<String, Object> entry: newValues.entrySet()) {
            boundSql.setAdditionalParameter(entry.getKey(),entry.getValue());
        }
    }

    /**
     * 加密注解
     * @param value 属性对应值
     * @return 加密处理后的值
     */
    private Object handleEncryptField(Object value) {
        Object newValue = value;
        if (value != null) {
            newValue = encrypt.encrypt(value.toString());
        }
        return newValue;
    }

//    if(isWriteCommand(commandType) && !SensitiveTypeRegisty.alreadyBeSentisived(newValue)) {
//        newValue = handleSensitiveField(field, newValue);
//        newValue = handleSensitiveJSONField(field, newValue);
//    }

    /**
    private boolean isWriteCommand(SqlCommandType commandType) {
        return SqlCommandType.UPDATE.equals(commandType) || SqlCommandType.INSERT.equals(commandType);
    }
    private Object handleSensitiveField(Field field, Object value) {
        SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
        Object newValue = value;
        if (sensitiveField != null && value != null) {
            newValue = SensitiveTypeRegisty.get(sensitiveField.value()).handle(value);
        }
        return newValue;
    }
    private Object handleSensitiveJSONField(Field field, Object value) {
        SensitiveJSONField sensitiveJSONField = field.getAnnotation(SensitiveJSONField.class);
        Object newValue = value;
        if (sensitiveJSONField != null && value != null) {
            newValue = processJsonField(newValue,sensitiveJSONField);
        }
        return newValue;
    }
    private Object processJsonField(Object newValue,SensitiveJSONField sensitiveJSONField) {
        try{
            Map<String,Object> map = JsonUtils.parseToObjectMap(newValue.toString());
            SensitiveJSONFieldKey[] keys =sensitiveJSONField.sensitivelist();
            for(SensitiveJSONFieldKey jsonFieldKey :keys){
                String key = jsonFieldKey.key();
                Object oldData = map.get(key);
                if(oldData!=null){
                    Object newData = handleEncryptField(oldData);
                    map.put(key,newData);
                }
            }
            return JsonUtils.parseMaptoJSONString(map);
        }catch (Throwable e){
            log.error("脱敏json串时失败，cause : {}",e.getMessage(),e);
            return newValue;
        }
    }
     */

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}
